package com.snow.demo.app.security.asymmetry;

import com.snow.demo.app.security.BusinessException;
import com.snow.demo.app.security.ByteUtils;
import com.snow.demo.app.security.CoreExceptionCodes;
import com.snow.demo.app.security.Crypto;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

public class RSA2048Cipher implements Crypto {

    private static Logger logger = LoggerFactory.getLogger(RSA2048Cipher.class);

    private KeyPair rsaKeys;

    private Cipher cipher;

    //1024   117 128
    //2048  245 256
    /**
     * RSA最大加密明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 245;

    /**
     * RSA最大解密密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 256;

    /**
     * RSA 加密算法
     */
    public static final String RSA_ALGORITHM = "RSA";

    /**
     * BC provider
     */
    public static final String BC_PROVIDER = "BC";

    /**
     * 构造RSA的加密
     *
     * @param priKey 加密后的私钥
     */
    public RSA2048Cipher(String pubKey, String priKey) {
        byte[] pubKeyBytes = Base64.decode(pubKey.getBytes());
        byte[] priKeyBytes = Base64.decode(priKey.getBytes());
        toKey(pubKeyBytes, priKeyBytes);
    }

    /**
     * 把字符数组转为Rsa的Key
     *
     * @param pubKey，priKey
     * @author 137127
     * @exception/throws BusinessException RSA_UTIS_KEY_GEN_ERROR
     */
    public void toKey(byte[] pubKey, byte[] priKey) {
        try {
            Security.addProvider(new BouncyCastleProvider());

            X509EncodedKeySpec pubX509 = new X509EncodedKeySpec(pubKey);
            PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(priKey);

            KeyFactory keyf = KeyFactory.getInstance(RSA_ALGORITHM, BC_PROVIDER);
            PublicKey pubkey = keyf.generatePublic(pubX509);
            PrivateKey privkey = keyf.generatePrivate(priPKCS8);
            this.rsaKeys = new KeyPair(pubkey, privkey);
            this.cipher = Cipher.getInstance(keyf.getAlgorithm());
        } catch (Exception e) {
            logger.error("公钥秘钥还原失败" + e.getMessage(), e);
            throw new BusinessException(CoreExceptionCodes.RSA_UTIS_KEY_GEN_ERROR);
        }
    }


    public byte[] decrypt(byte[] bytes) {
        try {
            Cipher c = getCipher(Cipher.DECRYPT_MODE, this.rsaKeys.getPrivate());
            int inputLen = bytes.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段解密
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                    cache = c.doFinal(bytes, offSet, MAX_DECRYPT_BLOCK);
                } else {
                    cache = c.doFinal(bytes, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_DECRYPT_BLOCK;
            }
            byte[] data = out.toByteArray();
            out.close();
            return data;
        } catch (Exception e) {
            logger.error(e.getMessage());
            throw new BusinessException(CoreExceptionCodes.DECRYPT_RSA_FAIL);
        }
    }

    public byte[] encrypt(byte[] bytes) {
        try {
            Cipher c = getCipher(Cipher.ENCRYPT_MODE, this.rsaKeys.getPublic());
            int inputLen = bytes.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段加密
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
                    cache = c.doFinal(bytes, offSet, MAX_ENCRYPT_BLOCK);
                } else {
                    cache = c.doFinal(bytes, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_ENCRYPT_BLOCK;
            }
            byte[] encryptedData = out.toByteArray();
            out.close();
            return encryptedData;
        } catch (Exception e) {
            logger.error(e.getMessage());
            throw new BusinessException(CoreExceptionCodes.ENCRYPT_RSA_FAIL);
        }
    }

    /**
     * 获取密码对象（用于加密解密）
     *
     * @param modeType 加密或者解密类型 Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE
     * @return
     * @author 137127
     */
    private Cipher getCipher(int modeType, Key key) {
        try {
            this.cipher.init(modeType, key);
            return this.cipher;
        } catch (InvalidKeyException e) {
            logger.error("获取加解密工具失败" + e.getMessage(), e);
            throw new BusinessException(CoreExceptionCodes.RSA_CIPHER_GET_FAIL);
        }
    }

    public static KeyPair genKeyPair() {
        try {
            Security.addProvider(new BouncyCastleProvider());
            KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(RSA_ALGORITHM, BC_PROVIDER);
            keyPairGen.initialize(2048, new SecureRandom());
            return keyPairGen.generateKeyPair();
        } catch (Exception e) {
            logger.error("生成RSA秘钥对失败" + e.getMessage(), e);
            throw new BusinessException(CoreExceptionCodes.RSA_GENERATOR_KEY_FAIL);
        }
    }

}
